// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <fmt/format.h>

#include "coin2/base/log.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/symbology/currency.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/flow/symbol_group.h"
#include <toml.hpp>

namespace coin2::flow {

namespace impl {

using namespace coin2::exchange::base::symbology;

/// we need topic string for
/// 1. coin2 live strat (for symbol or symbol_group)=> okex_feed_v3_a1, ..
/// 2. coin1 archive read (for symbol or symbol_group) => (coin1 topic strings)
/// 3. coin2 archive read => (coin2 topic strings)
/// 4. coin2 feed parser ioc topic with connection info => spot_okex_v3_a_0_1 {mea}_{group}_{connection}_{worker}

enum TopicType {
  UNKNOWN_TOPIC = 0,
  LONG_TOPIC,
  SHORT_TOPIC,
  CONNECTION_TOPIC,
};

/// two APIs
/// 1. GetCoin2TopicStrings(MarketExchangeApi, native_symbol)
/// 2. GetCoin12TopicStrings(MarketExchangeApi, nnative_symbol)

class TopicStringConfig {
 public:
  TopicStringConfig() {
    // TODO(donggu): the current working dir can be changed. include toml as str in the code.
    const auto data = toml::parse("data/coin2/feed/topic_strings.toml");
    map_ = toml::get<
        std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::string>>>>(
        data);
  }

  std::vector<std::string> Get(const MarketExchangeApi& mea) const {
    std::vector<std::string> out;

    auto it = map_.find(mea.SnakeString());
    if (it == map_.end()) {
      THROW() << "[TopicStringConfig] cannot find exchange: " << mea.SnakeString();
    }

    for (auto it2 : it->second) {
      out.insert(out.end(), it2.second.begin(), it2.second.end());
    }

    return out;  // if not exists, return {}
  }

  const std::vector<std::string>& ForMea(const MarketExchangeApi& mea, const std::string& feed_type)
      const {
    auto it = map_.find(mea.SnakeString());
    if (it == map_.end()) {
      THROW() << "[TopicStringConfig] cannot find exchange: " << mea.SnakeString();
    }
    auto it2 = it->second.find(feed_type);
    if (it2 == it->second.end()) {
      THROW() << "[TopicStringConfig] cannot find feed_type " << feed_type << " in "
              << mea.SnakeString();
    }
    return it2->second;  // if not exists, return {}
  }

  static const TopicStringConfig& GetInstance();

  /// example:
  /// abc_{group}{worker} / group='e', workers="123"
  /// abc_e1
  /// abc_e2
  /// abc_e3

  // product has no concept of api_version
  static std::vector<std::string>
  ForProduct(
    const MarketExchangeApi& mea,
    const IProduct& product,
    const std::string& workers,
    char group) {
    std::vector<std::string> out;
    for (auto& topic : TopicStringConfig::GetInstance().Get(mea)) {
      for (auto worker : workers) {
        std::string topic_string =
            fmt::format(topic, fmt::arg("group", group), fmt::arg("worker", worker));
        out.push_back(topic_string);
      }
    }
    return out;
  }

  static std::string StripWokerFromConnectionTopic(const std::string& topic) {
    auto const pos = topic.find_last_of('_');
    return topic.substr(0, pos);
  };

 private:
  std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::string>>> map_;
  std::unordered_map<std::string, uint64_t> string_to_id_;
  std::unordered_map<uint64_t, std::string> id_to_string_;
};

}  // namespace impl

using impl::TopicStringConfig;
using impl::TopicType;

}  // namespace coin2::flow
